#region Copyright (c) 2003, newtelligence AG. All rights reserved.
/*
// Copyright (c) 2003, newtelligence AG. (http://www.newtelligence.com)
// Original BlogX Source Code: Copyright (c) 2003, Chris Anderson (http://simplegeek.com)
// All rights reserved.
//  
// Redistribution and use in source and binary forms, with or without modification, are permitted 
// provided that the following conditions are met: 
//  
// (1) Redistributions of source code must retain the above copyright notice, this list of 
// conditions and the following disclaimer. 
// (2) Redistributions in binary form must reproduce the above copyright notice, this list of 
// conditions and the following disclaimer in the documentation and/or other materials 
// provided with the distribution. 
// (3) Neither the name of the newtelligence AG nor the names of its contributors may be used 
// to endorse or promote products derived from this software without specific prior 
// written permission.
//      
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS 
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER 
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -------------------------------------------------------------------------
//
// Original BlogX source code (c) 2003 by Chris Anderson (http://simplegeek.com)
// 
// newtelligence is a registered trademark of newtelligence Aktiengesellschaft.
// 
// For portions of this software, the some additional copyright notices may apply 
// which can either be found in the license.txt file included in the source distribution
// or following this notice. 
//
*/
#endregion

namespace newtelligence.DasBlog.Web
{
	using System;
	using System.IO;
	using System.Xml.Serialization;
	using System.Web;
	using System.Security.Principal;
	using System.Security.Cryptography;
	using System.Text;
	using System.Collections;
	using newtelligence.DasBlog.Runtime;
    using System.Collections.Generic;
    using System.Web.Profile;


    
	internal class SiteSecurity
	{
		public static bool IsInRole(string role)
		{
			return System.Web.Security.Roles.IsUserInRole(role);
		}

		public static void SetSecurity(SiteSecurityConfig ssc)
		{
			XmlSerializer ser = new XmlSerializer(typeof(SiteSecurityConfig));
			using (StreamWriter writer = new StreamWriter(System.Web.Hosting.HostingEnvironment.MapPath("~/SiteConfig/siteSecurity.config")))
			{
				ser.Serialize(writer, ssc);
			}
		}

		public static SiteSecurityConfig GetSecurity()
		{
			
			string fullPath = null;
			if (HttpContext.Current != null)
			{
				fullPath = System.Web.Hosting.HostingEnvironment.MapPath("~/SiteConfig/siteSecurity.config");
			}
			else //We are being call on a background thread...
			{	
				fullPath = Path.Combine(HttpRuntime.AppDomainAppPath,"SiteConfig/siteSecurity.config");
			}
																								
			// luke@jurasource.co.uk 18-MAY-04
			// When the current page is below the root directory, try the directory above
			// for the location of the security config file.
			//
			// Typically the siteSecurity.config file will not exist in the cases where
			// the page checking the current user is below the root web directory.  For
			// example, /ftb/ftb.insertcode.aspx, or /ftb/ftb.imagegallery.aspx
			//
			// Note that I have only seen this problem when NT Authentication is being
			// used at the site level.

			if ( !File.Exists(fullPath) )
			{
				fullPath = System.Web.Hosting.HostingEnvironment.MapPath("~/SiteConfig/siteSecurity.config");
			}
			if (File.Exists(fullPath))
			{
				  return GetSecurity( fullPath );
			}

			return new SiteSecurityConfig();
		}

		// make it easier to get the sitesecurity from a non-web app.
		public static SiteSecurityConfig GetSecurity( string path ){

			// argument validation
			if( path == null || path.Length == 0 ){
				throw new ArgumentNullException( "path" );
			}
			
			// setup the serializer
			XmlSerializer ser = new XmlSerializer(typeof(SiteSecurityConfig));

			// load the security config
			using (StreamReader reader = new StreamReader(path)) {
				return (SiteSecurityConfig)ser.Deserialize(reader);
			}
		}

		public static void AddUser(string userName, string password, string role, bool ask, string emailAddress)
		{
			SiteSecurityConfig ssc = GetSecurity();
			User user = new User();
			user.Name = userName;
			user.Password = password;
			user.Role = role;
			user.Ask = ask;
			user.EmailAddress = emailAddress;
			ssc.Users.Add(user);

			SetSecurity(ssc);
		}

        public static void UpdateUser(User user)
        {
            SiteSecurityConfig ssc = GetSecurity();
            foreach (User existingUser in ssc.Users)
            {
                if (existingUser.Name.ToUpper() == user.Name.ToUpper())
                {
                    existingUser.Role = user.Role;
                    existingUser.DisplayName = user.DisplayName;
                    existingUser.EmailAddress = user.EmailAddress;
                    existingUser.Active = user.Active;
                    existingUser.Ask = user.Ask;
                    existingUser.NotifyOnAllComment = user.NotifyOnAllComment;
                    existingUser.NotifyOnNewPost = user.NotifyOnNewPost;
                    existingUser.NotifyOnOwnComment = user.NotifyOnOwnComment;
                }
            }
            SetSecurity(ssc);
        }

		/// <summary>
		/// This function takes a password and the userName to
		/// compare the password with the password asigned to the userName.
		/// Both passwords, only one or none will exist as md5 hashed.  
		/// </summary>
		/// <param name="userName"></param>
		/// <param name="password"></param>
		/// <returns>user as UserToken.</returns>
		public static UserToken Login(string userName, string password)
		{
            UserToken token = GetUserToken(userName, password);

			if (token == null)
			{
				SiteSecurity.LogFailure(userName);
			}
			else
			{
				SiteSecurity.LogSuccess(token.Name);
				GenericIdentity identity = new GenericIdentity(token.Name, "Custom");
				GenericPrincipal principal = new GenericPrincipal(identity, new string[]{ token.Role });
				HttpContext.Current.User = principal;
				System.Threading.Thread.CurrentPrincipal = principal;
			}

			return token;
		}

        public static UserToken GetUserToken(string userName, string password)
        {
            UserToken token = null;
            SiteSecurityConfig ssc = GetSecurity();
            foreach (User user in ssc.Users)
            {
                if (user.Name.ToUpper() == userName.ToUpper() && user.Active)
                {
                    if ((IsCleanStringEncrypted(user.Password) && IsCleanStringEncrypted(password)) ||
                        (!IsCleanStringEncrypted(user.Password) && !IsCleanStringEncrypted(password)))
                    {
                        if (user.Password == password)
                        {
                            token = user.ToToken();
                            break;
                        }
                        else if (user.Password == SiteSecurity.Encrypt(password))
                        {
                            token = user.ToToken();
                            break;
                        }
                    }
                    else if ((IsCleanStringEncrypted(user.Password) && !IsCleanStringEncrypted(password)))
                    {
                        if (user.Password == Encrypt(password))
                        {
                            token = user.ToToken();
                            break;
                        }
                    }
                    else
                    {
                        if (Encrypt(user.Password) == password)
                        {
                            token = user.ToToken();
                            break;
                        }
                    }
                }
            }
            return token;
        }

		private static void LogFailure(string userName)
		{
			ILoggingDataService loggingService = LoggingDataServiceFactory.GetService(SiteConfig.GetLogPathFromCurrentContext());
			if (HttpContext.Current == null)
			{
				loggingService.AddEvent(new EventDataItem(EventCodes.SecurityFailure,userName,"non-web"));
			}
			else
			{
				loggingService.AddEvent(new EventDataItem(EventCodes.SecurityFailure,userName,HttpContext.Current.Request.UserHostAddress));
			}
		}

		private static void LogSuccess(string userName)
		{
			ILoggingDataService loggingService = LoggingDataServiceFactory.GetService(SiteConfig.GetLogPathFromCurrentContext());
			if (HttpContext.Current == null)
			{
				loggingService.AddEvent(new EventDataItem(EventCodes.SecuritySuccess,userName,"non-web"));
			}
			else
			{
				loggingService.AddEvent(new EventDataItem(EventCodes.SecuritySuccess,userName,HttpContext.Current.Request.UserHostAddress));
			}
		}
		
		/// <summary>
		/// This function takes a password, the  challenge and the userName to
		/// make an super challenge like on the client side. 
		/// </summary>
		/// <param name="userName"></param>
		/// <param name="clientHash"></param>
		/// <param name="challenge"></param>
		/// <returns>user as UserToken.</returns>
		public static UserToken Login(string userName, string clientHash, string challenge)
		{
			ILoggingDataService loggingService = LoggingDataServiceFactory.GetService(SiteConfig.GetLogPathFromCurrentContext());

			UserToken token = null;
			SiteSecurityConfig ssc = GetSecurity();
			foreach (User user in ssc.Users)
			{
				if (user.Name.ToUpper() == userName.ToUpper() && user.Active)
				{
					if (DoSuperChallenge(challenge, user.Password, userName, clientHash))
					{
						token = user.ToToken();
						break;
					}
				}
			}

			if (token == null)
			{
				SiteSecurity.LogFailure(userName);
			}
			else
			{
				SiteSecurity.LogSuccess(token.Name);
				GenericIdentity identity = new GenericIdentity(token.Name, "Custom");
				GenericPrincipal principal = new GenericPrincipal(identity, new string[]{ token.Role });
				HttpContext.Current.User = principal;
				System.Threading.Thread.CurrentPrincipal = principal;
			}

			return token;
		}

		/// <summary>
		/// This function builds from the challange, the (md5 hashed) password and the userName
		/// an md5 hashed string and compare them with the cryptPassword from the client. 
		/// </summary>
		/// <param name="challenge"></param>
		/// <param name="password"></param>
		/// <param name="userName"></param>
		/// <param name="clientHash"></param>
		/// <returns>true if coparing was good otherwise false</returns>
		public static bool DoSuperChallenge(string challenge, string passwordIN, string userName, string clientHash)
		{
			string password = Encrypt(passwordIN);
			string serverHash = Encrypt(challenge+password+userName);
			
			if (clientHash == serverHash)
				return true;
			else
				return false;
		}
		
		/// <summary>
		/// This function compute a given string with the md5 hash algorithm. 
		/// </summary>
		/// <param name="cleanString"></param>
		/// <returns>hash from cleanString as string.</returns>
		public static string Encrypt(string cleanString)
		{
			if (!IsCleanStringEncrypted(cleanString))
			{
				Byte[] clearBytes = new UnicodeEncoding().GetBytes(cleanString);
				Byte[] hashedBytes = ((HashAlgorithm) CryptoConfig.CreateFromName("MD5")).ComputeHash(clearBytes);
			
				return BitConverter.ToString(hashedBytes);
			}
			else
				return cleanString;
		}

		/// <summary>
		/// This function checks an given string if it is always encrypted 
		/// </summary>
		/// <param name="cleanString">clean string to check</param>
		/// <returns>true or false depending on string type.</returns>
		public static bool IsCleanStringEncrypted(string cleanString)
		{
			if (cleanString.Length == 47 && cleanString.Replace("-","").Length == 32)
				return true;
			else
				return false;
		}

		/// <summary>
		/// This function takes a password and then
		/// writes the siteSecurity.config file.
		/// </summary>
		/// <param name="userName">The username of the logged in user</param>
		/// <param name="password">The plain text password of the logged in user</param>
		public static void SetPassword(string userName, string password)
		{
			SiteSecurityConfig ssc = GetSecurity();
			foreach (User user in ssc.Users)
			{
				if (user.Name.ToUpper() == userName.ToUpper())
				{
					user.Password = password;
				}
			}

			// write out the changes
			SetSecurity(ssc);	
		}

		public static UserToken GetToken(string userName)
		{
			if (userName != null)
			{
				SiteSecurityConfig ssc = GetSecurity();
				foreach (User user in ssc.Users)
				{
					if (user.Name.ToUpper() == userName.ToUpper())
					{
						return user.ToToken();
					}
				}
			}
			return null;
		}

		/// <summary>
		/// This function looks up a given username and returns the associated
		/// User object.
		/// </summary>
		/// <param name="userName">The username to look up.</param>
		/// <returns>The User object corresponding to the provided username.</returns>
		public static User GetUser(string userName)
		{
			if (userName != null)
			{
				SiteSecurityConfig ssc = GetSecurity();
				foreach (User user in ssc.Users)
				{
					if (user.Name.ToUpper() == userName.ToUpper())
					{
						return user;
					}
				}
			}
			return null;
		}

        public static void DeleteUser(string userName)
        {
            if (userName != null)
            {
                SiteSecurityConfig ssc = GetSecurity();
                foreach (User user in ssc.Users)
                {
                    if (user.Name.ToUpper() == userName.ToUpper())
                    {
                        ssc.Users.Remove(user);
                        break;
                    }
                }
                SetSecurity(ssc);
            }
        }

        public static IEnumerable<User> GetUsersInRole(string roleName)
        {
            SiteSecurityConfig ssc = GetSecurity();
            foreach (User user in ssc.Users)
            {
                if (string.Compare(user.Role, "admin", true)==0 ||
                    string.Compare(user.Role, roleName, true)==0)
                {
                    yield return user;
                }
            }
        }

        public static IEnumerable<User> GetUsers()
        {
            SiteSecurityConfig ssc = GetSecurity();
            foreach (User user in ssc.Users)
            {
               yield return user;
            }
        }

		/// <summary>
		/// This function looks up a given username and returns the associated
		/// User object.
		/// </summary>
		/// <param name="userName">The username to look up.</param>
		/// <returns>The User object corresponding to the provided username.</returns>
		public static User GetUserByEmail(string email)
		{
            if ( email == null )
                return null;

			SiteSecurityConfig ssc = GetSecurity();
			foreach (User user in ssc.Users)
			{
                if (user.EmailAddress != null)
                {
                    if (user.EmailAddress.ToUpper() == email.ToUpper())
                    {
                        return user;
                    }
                }
			}

			return null;
		}

        public static User GetUserByDisplayName( string displayName )
        {
            SiteSecurityConfig ssc = GetSecurity();
            displayName = displayName.ToUpper();

            foreach ( User user in ssc.Users )
            {
                if ( user.DisplayName != null && user.DisplayName.ToUpper() == displayName )
                {
                    return user;
                }
            }

            return null;
        }
	}

	public class SiteSecurityConfig
	{
		UserCollection users = new UserCollection();

		public UserCollection Users { get { return users; } }
	}

	internal class UserToken
	{
		string name;
		string role;

		public string Name { get { return name; } set { name = value; } }
		public string Role { get { return role; } set { role = value; } }
	}

	public class User
	{
		string name;
		string password;
		string role;
		bool ask;
		string emailAddress;
		string displayName;
		
		bool notifyOnNewPost = false;
		bool notifyOnAllComment = false;
		bool notifyOnOwnComment = false;
		bool active = true;

		public string Name { get { return name; } set { name = value; } }
		[XmlIgnore]
		public string Password { get { return password; } set { password = SiteSecurity.Encrypt(value); } }
		public string Role { get { return role; } set { role = value; } }
		public bool Ask { get { return ask; } set { ask = value; } }
		public string EmailAddress { get { return emailAddress; } set { emailAddress = value; } }
		public string DisplayName { get { return displayName; } set { displayName = value; } }

		public bool NotifyOnNewPost { get { return notifyOnNewPost; } set { notifyOnNewPost = value; } }
		public bool NotifyOnAllComment { get { return notifyOnAllComment; } set { notifyOnAllComment = value; } }
		public bool NotifyOnOwnComment { get { return notifyOnOwnComment; } set { notifyOnOwnComment = value; } }
		public bool Active { get { return active; } set { active = value; } }

		/// <summary>
		/// Encrypted password for serialization.
		/// </summary>
		[XmlElement("Password")]
		public string XmlPassword
		{
			get
			{
				return Password;
			}
			set
			{
				password = value;
			}
		}

		internal UserToken ToToken()
		{
			UserToken token = new UserToken();
			token.Name = name;
			token.Role = role;
			return token;
		}
	}

	#region UserCollection
	/// <summary>
	/// A collection of elements of type User
	/// </summary>
	public class UserCollection: System.Collections.CollectionBase
	{
		/// <summary>
		/// Initializes a new empty instance of the UserCollection class.
		/// </summary>
		public UserCollection()
		{
			// empty
		}

		/// <summary>
		/// Initializes a new instance of the UserCollection class, containing elements
		/// copied from an array.
		/// </summary>
		/// <param name="items">
		/// The array whose elements are to be added to the new UserCollection.
		/// </param>
		public UserCollection(User[] items)
		{
			this.AddRange(items);
		}

		/// <summary>
		/// Initializes a new instance of the UserCollection class, containing elements
		/// copied from another instance of UserCollection
		/// </summary>
		/// <param name="items">
		/// The UserCollection whose elements are to be added to the new UserCollection.
		/// </param>
		public UserCollection(UserCollection items)
		{
			this.AddRange(items);
		}

		/// <summary>
		/// Adds the elements of an array to the end of this UserCollection.
		/// </summary>
		/// <param name="items">
		/// The array whose elements are to be added to the end of this UserCollection.
		/// </param>
		public virtual void AddRange(User[] items)
		{
			foreach (User item in items)
			{
				this.List.Add(item);
			}
		}

		/// <summary>
		/// Adds the elements of another UserCollection to the end of this UserCollection.
		/// </summary>
		/// <param name="items">
		/// The UserCollection whose elements are to be added to the end of this UserCollection.
		/// </param>
		public virtual void AddRange(UserCollection items)
		{
			foreach (User item in items)
			{
				this.List.Add(item);
			}
		}

		/// <summary>
		/// Adds an instance of type User to the end of this UserCollection.
		/// </summary>
		/// <param name="value">
		/// The User to be added to the end of this UserCollection.
		/// </param>
		public virtual void Add(User value)
		{
			this.List.Add(value);
		}

		/// <summary>
		/// Determines whether a specfic User value is in this UserCollection.
		/// </summary>
		/// <param name="value">
		/// The User value to locate in this UserCollection.
		/// </param>
		/// <returns>
		/// true if value is found in this UserCollection;
		/// false otherwise.
		/// </returns>
		public virtual bool Contains(User value)
		{
			return this.List.Contains(value);
		}

		/// <summary>
		/// Return the zero-based index of the first occurrence of a specific value
		/// in this UserCollection
		/// </summary>
		/// <param name="value">
		/// The User value to locate in the UserCollection.
		/// </param>
		/// <returns>
		/// The zero-based index of the first occurrence of the _ELEMENT value if found;
		/// -1 otherwise.
		/// </returns>
		public virtual int IndexOf(User value)
		{
			return this.List.IndexOf(value);
		}

		/// <summary>
		/// Inserts an element into the UserCollection at the specified index
		/// </summary>
		/// <param name="index">
		/// The index at which the User is to be inserted.
		/// </param>
		/// <param name="value">
		/// The User to insert.
		/// </param>
		public virtual void Insert(int index, User value)
		{
			this.List.Insert(index, value);
		}

		/// <summary>
		/// Gets or sets the User at the given index in this UserCollection.
		/// </summary>
		public virtual User this[int index]
		{
			get
			{
				return (User) this.List[index];
			}
			set
			{
				this.List[index] = value;
			}
		}

		/// <summary>
		/// Removes the first occurrence of a specific User from this UserCollection.
		/// </summary>
		/// <param name="value">
		/// The User value to remove from this UserCollection.
		/// </param>
		public virtual void Remove(User value)
		{
			this.List.Remove(value);
		}

		/// <summary>
		/// Type-specific enumeration class, used by UserCollection.GetEnumerator.
		/// </summary>
		public class Enumerator: System.Collections.IEnumerator
		{
			private System.Collections.IEnumerator wrapped;

			public Enumerator(UserCollection collection)
			{
				this.wrapped = ((System.Collections.CollectionBase)collection).GetEnumerator();
			}

			public User Current
			{
				get
				{
					return (User) (this.wrapped.Current);
				}
			}

			object System.Collections.IEnumerator.Current
			{
				get
				{
					return (User) (this.wrapped.Current);
				}
			}

			public bool MoveNext()
			{
				return this.wrapped.MoveNext();
			}

			public void Reset()
			{
				this.wrapped.Reset();
			}
		}

		/// <summary>
		/// Returns an enumerator that can iterate through the elements of this UserCollection.
		/// </summary>
		/// <returns>
		/// An object that implements System.Collections.IEnumerator.
		/// </returns>        
		public new virtual UserCollection.Enumerator GetEnumerator()
		{
			return new UserCollection.Enumerator(this);
		}
		public void Sort(IComparer comparer)
		{
			this.InnerList.Sort(comparer);
		}
	}
	#endregion
}
